﻿using System;
using System.Linq;
using System.Reflection;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using ProLinq.Projection;
using ProLinq.Projection.Configuration;

namespace ProLinq.Tests.Projection
{
	[TestClass]
	public class ConfigurationFactoryTests
	{
		[TestMethod]
		public void DefaultFactoryIsNotNull()
		{
			IProjectionConfigurationFactory factory = ProjectionConfigurationFactory.Instance;
			Assert.IsNotNull(factory);
		}

		[TestMethod]
		public void DefaultConfigurationIsNotNull()
		{
			IProjectionConfigurationFactory factory = ProjectionConfigurationFactory.Instance;
			ProjectionConfiguration<A, B> cfg = factory.CreateConfiguration<A, B>();
			Assert.IsNotNull(cfg);
		}

		[TestMethod]
		public void DefaultConfigurationIsValid()
		{
			IProjectionConfigurationFactory factory = ProjectionConfigurationFactory.Instance;
			ProjectionConfiguration<A, B> cfg = factory.CreateConfiguration<A, B>();

			Assert.IsNotNull(cfg.MainProjection.Factory, "Configuration factory method is null");
			Assert.IsNotNull(cfg.MainProjection.Map, "Configuration property map is null");


			// factory tests
			var a = new A
				{
					DataInt1 = 1,
					DataInt2 = 2,
					DataInt3 = 3,
					DataString1 = "4",
					DataString2 = "5"
				};

			B b = cfg.MainProjection.Factory(a);

			Assert.IsNotNull(b, "Configuration factory returns null");
			Assert.AreEqual(a.DataInt2, b.DataInt2, "BackFactory failed to map matching fields");
			Assert.AreEqual(a.DataString1, b.DataString1, "BackFactory failed to map matching fields");


			// map tests
			Assert.IsTrue(cfg.MainProjection.Map.All(m => m.SourceProperty != null), "A source property is null in the map");
			Assert.IsTrue(cfg.MainProjection.Map.All(m => m.DestinationProperty != null), "A destination property is null in the map");
			Assert.IsTrue(cfg.MainProjection.Map.All(m => m.SourceProperty.DeclaringType == typeof(A)),
			              "A source property in the map belongs to invalid type");
			Assert.IsTrue(cfg.MainProjection.Map.All(m => m.DestinationProperty.DeclaringType == typeof(B)),
			              "A destination property in the map belongs to invalid type");
			Assert.IsTrue(cfg.MainProjection.Map.All(m => m.SourceProperty.Name == m.DestinationProperty.Name),
			              "Properties in the default map should have the same names");
			Assert.IsTrue(cfg.MainProjection.Map.All(m => m.SourceProperty is FieldInfo || m.SourceProperty is PropertyInfo),
			              "A source property is nor field nor property.");
			Assert.IsTrue(
				cfg.MainProjection.Map.All(m => m.DestinationProperty is FieldInfo || m.DestinationProperty is PropertyInfo),
				"A destination property is nor field nor property.");
			Assert.IsTrue(cfg.MainProjection.Map.All(m => GetMemberType(m.SourceProperty) == GetMemberType(m.DestinationProperty)),
			              "Property types don't match");
		}

		[TestMethod]
		public void LambdaConfigurationIsValid()
		{
			IProjectionConfigurationFactory factory = ProjectionConfigurationFactory.Instance;
			ProjectionConfiguration<A, C> cfg = factory.CreateConfiguration<A, C>(a1 => new C(a1.DataString1)
				{
					DataInt1 = a1.DataInt1.ToString(), // no projection, but factory construction
					DataInt2 = a1.DataInt2,
					DataString3 = a1.DataString2 // different names mapping
				});

			Assert.IsNotNull(cfg.MainProjection.Factory, "Configuration factory method is null");
			Assert.IsNotNull(cfg.MainProjection.Map, "Configuration property map is null");

			// factory tests
			var a = new A
				{
					DataInt1 = 1,
					DataInt2 = 2,
					DataInt3 = 3,
					DataString1 = "4",
					DataString2 = "5"
				};

			C c = cfg.MainProjection.Factory(a);

			Assert.IsNotNull(c, "Configuration factory returns null");
			Assert.AreEqual(a.DataInt2, c.DataInt2, "BackFactory failed to map matching fields");
			Assert.AreEqual(a.DataString1, c.DataString1, "BackFactory failed to map matching fields");
			Assert.AreEqual(a.DataInt1.ToString(), c.DataInt1, "BackFactory failed to map matching fields");
			Assert.AreEqual(a.DataString2, c.DataString3, "BackFactory failed to map matching fields");


			// map tests
			Assert.IsTrue(cfg.MainProjection.Map.All(m => m.SourceProperty != null), "A source property is null in the map");
			Assert.IsTrue(cfg.MainProjection.Map.All(m => m.DestinationProperty != null), "A destination property is null in the map");
			Assert.IsTrue(cfg.MainProjection.Map.All(m => m.SourceProperty.DeclaringType == typeof(A)),
			              "A source property in the map belongs to invalid type");
			Assert.IsTrue(cfg.MainProjection.Map.All(m => m.DestinationProperty.DeclaringType == typeof(C)),
			              "A destination property in the map belongs to invalid type");
			Assert.IsTrue(cfg.MainProjection.Map.All(m => m.SourceProperty is FieldInfo || m.SourceProperty is PropertyInfo),
			              "A source property is nor field nor property.");
			Assert.IsTrue(
				cfg.MainProjection.Map.All(m => m.DestinationProperty is FieldInfo || m.DestinationProperty is PropertyInfo),
				"A destination property is nor field nor property.");
			Assert.IsTrue(cfg.MainProjection.Map.All(m => GetMemberType(m.SourceProperty) == GetMemberType(m.DestinationProperty)),
			              "Property types don't match");
		}

		[TestMethod]
		public void DefaultConfigurationIsCached()
		{
			IProjectionConfigurationFactory factory = ProjectionConfigurationFactory.Instance;
			ProjectionConfiguration<A, B> cfg1 = factory.CreateConfiguration<A, B>();
			ProjectionConfiguration<A, B> cfg2 = factory.CreateConfiguration<A, B>();

			Assert.AreSame(cfg1.MainProjection.Factory, cfg2.MainProjection.Factory);
		}

		#region Helpers

		private Type GetMemberType(MemberInfo member)
		{
			if (member is FieldInfo)
				return ((FieldInfo) member).FieldType;
			if (member is PropertyInfo)
				return ((PropertyInfo) member).PropertyType;
			return null;
		}

		#endregion
	}
}